package lexico;

import java.io.IOException;
import java.io.InputStream;

import utils.Token;

public class AnalizadorLexico {

	private EstadoLexico estado;
	private int filaInicio;
	private int fila;
	private int colInicio;
	private int col;
	private String lexema;
	private int caracter;
	private InputStream input;
	private static final int EOF = -1;

	public AnalizadorLexico(InputStream i) throws IOException {
		input = i;
		fila = 0;
		col = -1;
		caracter = this.sigCar();
	}

	public Token nextToken() throws IOException {
		estado = EstadoLexico.ESTADO_INICIAL;
		filaInicio = fila;
		colInicio = col;
		lexema = "";

		while (true) {
			switch (estado) {

			case ESTADO_INICIAL:
				if (esNumPos(caracter))
					transita(EstadoLexico.ESTADO_NUM);
				else if (esComienzoIden(caracter))
					transita(EstadoLexico.ESTADO_IDEN);
				else if (esComienzoComparador(caracter))
					transita(EstadoLexico.ESTADO_COMPARADOR);
				else if (caracter == '+')
					transita(EstadoLexico.ESTADO_MAS);
				else if (caracter == '-')
					transita(EstadoLexico.ESTADO_MENOS);
				else if (caracter == '*')
					transita(EstadoLexico.ESTADO_POR);
				else if (caracter == '/')
					transita(EstadoLexico.ESTADO_DIV);
				else if (caracter == '0')
					transita(EstadoLexico.ESTADO_CERO);
				else if (caracter == '(')
					transita(EstadoLexico.ESTADO_PARENTESIS_APERTURA);
				else if (caracter == ')')
					transita(EstadoLexico.ESTADO_PARENTESIS_CIERRE);
				else if (caracter == '{')
					transita(EstadoLexico.ESTADO_LLAVE_APERTURA);
				else if (caracter == '}')
					transita(EstadoLexico.ESTADO_LLAVE_CIERRE);
				else if (caracter == ',')
					transita(EstadoLexico.ESTADO_COMA);
				else if (caracter == EOF)
					transita(EstadoLexico.ESTADO_EOF);
				else if (esSeparador(caracter))
					transitaIgnorando(EstadoLexico.ESTADO_INICIAL);
				else
					errorLexico();
				break;

			case ESTADO_CERO:
				return new Token(filaInicio, colInicio, CatLexica.NUM, lexema);

			case ESTADO_MAS:
				return new Token(filaInicio, colInicio, CatLexica.MAS);

			case ESTADO_MENOS:
				return new Token(filaInicio, colInicio, CatLexica.MENOS);

			case ESTADO_POR:
				return new Token(filaInicio, colInicio, CatLexica.POR);

			case ESTADO_DIV:
				return new Token(filaInicio, colInicio, CatLexica.DIV);

			case ESTADO_SET:
				return new Token(filaInicio, colInicio, CatLexica.SET);

			case ESTADO_TO:
				return new Token(filaInicio, colInicio, CatLexica.TO);

			case ESTADO_PARENTESIS_APERTURA:
				return new Token(filaInicio, colInicio, CatLexica.PARENTESIS_APERTURA);

			case ESTADO_PARENTESIS_CIERRE:
				return new Token(filaInicio, colInicio, CatLexica.PARENTESIS_CIERRE);
			
			case ESTADO_CALL:
				return new Token(filaInicio, colInicio, CatLexica.CALL);

			case ESTADO_EOF:
				return new Token(filaInicio, colInicio, CatLexica.EOF);

			case ESTADO_IDEN:
				if (esComienzoIden(caracter) || esDigito(caracter) || esFinal(caracter))
					transita(EstadoLexico.ESTADO_IDEN);
				else
					return reservada();
				break;

			case ESTADO_NUM:
				if (esDigito(caracter))
					transita(EstadoLexico.ESTADO_NUM);
				else
					return new Token(filaInicio, colInicio, CatLexica.NUM,
							lexema);
				break;

			case ESTADO_LLAVE_APERTURA:
				return new Token(filaInicio, colInicio, CatLexica.LLAVE_APERTURA);

			case ESTADO_LLAVE_CIERRE:
				return new Token(filaInicio, colInicio, CatLexica.LLAVE_CIERRE);

			case ESTADO_COMA:
				return new Token(filaInicio, colInicio, CatLexica.COMA);

			case ESTADO_COM:
				if (caracter != '\n' && caracter != EOF)
					transitaIgnorando(EstadoLexico.ESTADO_COM);
				else if (caracter == '\n')
					transitaIgnorando(EstadoLexico.ESTADO_INICIAL);
				else
					transitaIgnorando(EstadoLexico.ESTADO_EOF);
				
			case ESTADO_COMPARADOR:
				if (esParteComparador(caracter))
					transita(EstadoLexico.ESTADO_COMPARADOR);
				else
					return reservada();
				break;
			}

		}

	}

	private Token reservada() {
		if (lexema.equals("decs"))
			return new Token(filaInicio, colInicio, CatLexica.DECS);
		else if (lexema.equals("end decs"))
			return new Token(filaInicio, colInicio, CatLexica.END_DECS);
		else if (lexema.equals("body"))
			return new Token(filaInicio, colInicio, CatLexica.BODY);
		else if (lexema.equals("end body"))
			return new Token(filaInicio, colInicio, CatLexica.END_BODY);
		else if (lexema.equals("proc"))
			return new Token(filaInicio, colInicio, CatLexica.PROC);
		else if (lexema.equals("if"))
			return new Token(filaInicio, colInicio, CatLexica.IF);
		else if (lexema.equals("then"))
			return new Token(filaInicio, colInicio, CatLexica.THEN);
		else if (lexema.equals("else"))
			return new Token(filaInicio, colInicio, CatLexica.ELSE);
		else if (lexema.equals("call"))
			return new Token(filaInicio, colInicio, CatLexica.CALL);
		else if (lexema.equals("end call"))
			return new Token(filaInicio, colInicio, CatLexica.END_CALL);
		else if (lexema.equals("with"))
			return new Token(filaInicio, colInicio, CatLexica.WITH);
		else if (lexema.equals("out"))
			return new Token(filaInicio, colInicio, CatLexica.OUT);
		else if (lexema.equals("and"))
			return new Token(filaInicio, colInicio, CatLexica.AND);
		else if (lexema.equals("or"))
			return new Token(filaInicio, colInicio, CatLexica.OR);
		else if (lexema.equals("not"))
			return new Token(filaInicio, colInicio, CatLexica.NOT);
		else if (lexema.equals("="))
			return new Token(filaInicio, colInicio, CatLexica.IGUAL);
		else if (lexema.equals("<"))
			return new Token(filaInicio, colInicio, CatLexica.MENOR);
		else if (lexema.equals(">"))
			return new Token(filaInicio, colInicio, CatLexica.MAYOR);
		else if (lexema.equals("<="))
			return new Token(filaInicio, colInicio, CatLexica.MENOR_IGUAL);
		else if (lexema.equals(">="))
			return new Token(filaInicio, colInicio, CatLexica.MAYOR_IGUAL);
		else if (lexema.equals("=/="))
			return new Token(filaInicio, colInicio, CatLexica.DISTINTO);
		else if (lexema.equals("proc"))
			return new Token(filaInicio, colInicio, CatLexica.PROC);
		else if (lexema.equals("var"))
			return new Token(filaInicio, colInicio, CatLexica.VAR);
		else if (lexema.equals("set"))
			return new Token(filaInicio, colInicio, CatLexica.SET);
		else if (lexema.equals("to"))
			return new Token(filaInicio, colInicio, CatLexica.TO);
		else
			return new Token(filaInicio, colInicio, CatLexica.IDEN, lexema);
	}

	private void transita(EstadoLexico aEstado) throws IOException {
		lexema = lexema + (char) caracter;
		transitaIgnorando(aEstado);
	}

	private void transitaIgnorando(EstadoLexico aEstado) throws IOException {
		estado = aEstado;
		filaInicio = fila;
		colInicio = col;
		caracter = sigCar();

	}

	private int sigCar() throws IOException {
		caracter = input.read();
		if (caracter == '\n') {
			col = -1;
			fila++;
		} else {
			col++;
		}
		return caracter;
	}

	private boolean esComienzoIden(int c) {
		return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_'));
	}

	private boolean esSeparador(int c) {
		return (c == ' ' || c == '\t' || c == '\n' || c == '\r');
	}

	private boolean esNumPos(int c) {
		return (c >= '1' && c <= '9');
	}

	private boolean esDigito(int c) {
		return (esNumPos(c) || c == '0');
	}
	
	private boolean esComienzoComparador(int c) { // =, <, >
		return c == '=' || c == '>' || c == '<';
	}
	
	private boolean esParteComparador(int c) { // =/=, <=, >=
		return c == '=' || c == '/';
	}
	
	private boolean esFinal(int c) {
		if (lexema.equals("end") && c == ' ')
			return true;
		else
			return false;
	}

	private void errorLexico() {
		System.err.println("(" + fila + "," + col + ")"
				+ "ERROR LEXICO: caracter desconocido: " + (char) caracter);
		System.exit(1);

	}
	
//	public static void main(String[] args) throws IOException {
//		AnalizadorLexico aLex = new AnalizadorLexico(System.in);
//		Token t;
//		do {
//			t = aLex.nextToken();
//			System.out.println(t);
//		} while (t.getCategoria() != CatLexica.EOF);
//	}

}
